import { markdownToAnnotatedText } from "..";
import { linter, setDiagnostics, forEachDiagnostic, type Diagnostic } from "@codemirror/lint";
import elt from "crelt";
import { EditorView } from "@codemirror/view";
import { type AnnotatedText } from "./annotatedtext";


const MAX_REPLACEMENTS = 5;
const SPELLING_RULE_IDS = [
  "SPELLER_RULE",
  "MORFOLOGIK_RULE",
  "HUNSPELL",
  "SPELLING_RULE"
];
const STYLE_ISSUE_TYPES = [
  "style",
  "locale-violation",
  "register"
];


export function spellcheck({ performSpellcheckRequest, performSpellcheckAddWordRequest }: {
  performSpellcheckRequest: (request: any) => Promise<any>,
  performSpellcheckAddWordRequest?: (request: any) => Promise<void>,
}) {
  return linter(async (view) => {
    const initial = view.state.doc.toString();
    const annotatedText = markdownToAnnotatedText(initial);
    if (annotatedText.length === 0) {
      return [];
    }

    const spellcheckResults = await performSpellcheckRequest({
      data: {annotation: annotatedText},
    });

    if (initial !== view.state.doc.toString()) {
      // State changed during spellcheck request: ignore results. 
      // The linter will be called again for the new/changed state.
      return [];
    }
    
    return spellcheckResults.matches.map((m: any) => {
      const severity = SPELLING_RULE_IDS.some(r => m.rule.id.includes(r)) ? 'error' :
                       STYLE_ISSUE_TYPES.some(r => m.rule.id.includes(r)) ? 'info' : 
                       'warning';
      const from = m.offset;
      const to = Math.min(m.offset + m.length, view.state.doc.length);
      return {
        from,
        to,
        severity: severity,
        renderMessage() {
          return elt('div', {class: 'cm-spellcheck-content'},
            elt('div', {class: 'cm-spellcheck-title cm-spellcheck-title-' + severity}, m.rule.category.name),
            elt('div', {class: 'cm-spellcheck-message'}, 
              m.message,
              m.rule.urls?.length > 0 ? elt('a', {href: m.rule.urls[0].value, target: '_blank', rel: 'noreferrer noopener', class: 'cm-spellcheck-details'}) : null,
            ),
          );
        },
        actions: m.replacements.slice(0, MAX_REPLACEMENTS).map((r: any) => ({
          name: r.value,
          apply(view: EditorView, from: number, to: number) {
            view.dispatch({changes: {from, to, insert: r.value}});
          }
        })).concat((performSpellcheckAddWordRequest && severity === 'error') ? [{
          name: `Add "${view.state.doc.sliceString(from, to)}" to dictionary`,
          async apply(view: EditorView, from: number, to: number) {
            const word = view.state.doc.sliceString(from, to);
            await performSpellcheckAddWordRequest({ word });
            
            // remove spellcheck errors for word
            const diagnostics = [] as Diagnostic[]
            forEachDiagnostic(view.state, (d, from, to) => {
              if (!(d.severity === 'error' && view.state.doc.sliceString(from, to) == word)) {
                diagnostics.push(d);
              }
            });
            view.dispatch(setDiagnostics(view.state, diagnostics));
          }
        }] : []),
      };
    });
  }, {delay: 1500});
}


function svg(content: string, attrs = `viewBox="0 0 40 40"`) {
  return `url(') 50% no-repeat',
    backgroundSize: '100% 100%',
    display: 'inline-block',
    marginBottom: '-0.3em',
    marginLeft: '0.2em',
  },
  '.cm-spellcheck-title': {
    fontWeight: 'bold',
    paddingLeft: '1.3em',
  },
  '.cm-spellcheck-title:before': {
    content: '""',
    height: '0.6em',
    width: '0.6em',
    borderRadius: '50%',
    position: 'absolute',
    left: '1em',
    top: '0.5em',
  },
  '.cm-spellcheck-title-error:before': { background: '#f91a47' },
  '.cm-spellcheck-title-warning:before': { background: 'orange' },
  '.cm-spellcheck-title-info:before': { background: '#5c4cff' },

  ".cm-diagnostic-error": { borderLeft: "5px solid #f91a47" },
  ".cm-diagnostic-warning": { borderLeft: "5px solid orange" },
  ".cm-diagnostic-info": { borderLeft: "5px solid #5c4cff" },

  '.cm-lintRange-error': { backgroundImage: underline('#f91a47') },
  '.cm-lintRange-warning': { backgroundImage: underline('orange') },
  '.cm-lintRange-info': { backgroundImage: underline('#5c4cff') },
});
